
var modctx = {
	checkContextOpen: function() {
		if (modctx.item || modctx.armor || modctx.wpn)
			throw 'missing #end';
	},
	commands: {
		modname: function(args) {
			modctx.loadedmods.push(args.s);	
		},		
		selectitem: function(args) {
			modctx.checkContextOpen();
			
			var o;
			if (args.s)
				o = modctx.itemlookup[args.s];
			else if (args.n1)
				o = modctx.itemlookup[args.n1];
			else throw 'missing argument';
			if (!o) throw 'data not found';
			modctx.item = o;
		},
		newitem: function(args) {
			modctx.checkContextOpen();
			var id = modctx.itemdata.length;
			while (modctx.itemlookup[String(id)]) {
				id ++;
			}				
			var o = modctx.item = { id:id };
			modctx.itemdata.push(o);
			modctx.itemlookup[id] = o;
		},
		selectarmor: function(args) {
			modctx.checkContextOpen();
			
			var o;
			if (args.s)
				o = modctx.armorlookup[args.s];
			else if (args.n1)
				o = modctx.armorlookup[args.n1];
			else throw 'missing argument';
			if (!o) throw 'data not found';
			modctx.armor = o;
		},
		newarmor: function(args) {
			modctx.checkContextOpen();
			
			if (!args.n1 || args.n1<200 || args.n1>399 || modctx.armorlookup[args.n1])
				throw 'invalid id';
			
			var o = modctx.armor = { id: args.n1 };
			modctx.armordata.push(o);
			modctx.armorlookup[args.n1] = o;
		},
		selectweapon: function(args) {
			modctx.checkContextOpen();
			
			var o;
			if (args.s)
				o = modctx.wpnlookup[args.s];
			else if (args.n1)
				o = modctx.wpnlookup[args.n1];
			else throw 'missing argument';
			if (!o) {
				//workaround: apparently selectweapon on non existing id acts as newweapon
				if (args.n1)
					return modctx.commands.newweapon(args);
				else
					throw 'data not found';
			}
			modctx.wpn = o;
		},
		newweapon: function(args) {
			modctx.checkContextOpen();
			
			if (!args.n1 || args.n1<600 || args.n1>999 || modctx.wpnlookup[args.n1])
				throw 'invalid id';
			
			var o = modctx.wpn = { id: args.n1 };
			modctx.wpndata.push(o);
			modctx.wpnlookup[args.n1] = o;
		},
		end: function(args) {
			if (modctx.item)
				modctx.item = null;
			else if (modctx.armor) {
				if (!modctx.armor.armorname) throw 'unnamed armor';
				modctx.armor = null;
			}
			else if (modctx.wpn) {
				if (!modctx.wpn.wpname) throw 'unnamed weapon';
				modctx.wpn = null;
			}
			//else throw 'unmatched #end';
		},
		
		name: function(args) {
			if (modctx.item) {
				if (modctx.item.name) delete modctx.itemlookup[modctx.item.name];
				modctx.item.name = args.s;
				modctx.itemlookup[args.s] = modctx.item;
			}
			else if (modctx.armor) {
				if (modctx.armor.armorname) delete modctx.armorlookup[modctx.armor.armorname];
				modctx.armor.armorname = args.s;
				modctx.armorlookup[args.s] = modctx.armor;
			}
			else if (modctx.wpn) {
				if (modctx.wpn.wpname) delete modctx.wpnlookup[modctx.wpn.wpname];
				modctx.wpn.wpname = args.s;
				modctx.wpnlookup[args.s] = modctx.wpn;
			}
			//else throw 'command out of context';
		},
		descr: function(args) {
			if (modctx.item)
				modctx.item.descr = args.s;
			//else throw 'command out of context';
		},
		type: function(args) {
			if (modctx.item)
				modctx.item.type = modconstants['19'][args.n1];
			else if (modctx.armor)
				modctx.armor.type = args.n1;
			//else throw 'command out of context';
		},
		def: function(args) {
			if (modctx.armor)
				modctx.armor.def = args.n1;
			else if (modctx.wpn)
				modctx.wpn.def = args.n1;
			//else throw 'command out of context';
		},
		rcost: function(args) {
			if (modctx.armor)
				modctx.armor.res = args.n1;
			if (modctx.wpn)
				modctx.wpn.res = args.n1;
			//else throw 'command out of context';
		},
		
		
		//item specific
		weapon:	function(a) { modctx.setEither('item', 'wpn', a.n1, a.s); },
		armor:	function(a) { modctx.setVal('item', 'armor', a.s); },
		constlevel:	function(a) { modctx.setVal('item', 'con', a.n1); },
		mainpath:	function(a) { modctx.setVal('item', 'p1', modconstants['16'][a.n1]); },
		secondarypath:	function(a) { modctx.setVal('item', 'p2', modconstants['16'][a.n1]); },
		mainlevel:	function(a) { modctx.setVal('item', 'lv1', a.n1); },
		secondarylevel:	function(a) { modctx.setVal('item', 'lv2', a.n1); },
		copyspr:	function(a) { modctx.setEither('item', 'copyspr', a.n1, a.s); },
		
		
		//armor specific
		prot:	function(a) { modctx.setVal('armor', 'prot', a.n1); },
		enc:	function(a) { modctx.setVal('armor', 'enc', a.n1); },
	
		
		//weapon specific
		dmg:	function(a) { modctx.setVal('wpn', 'dmg', a.n1); },
		nratt:	function(a) { modctx.setVal('wpn', '#att', a.n1); },
		att:	function(a) { modctx.setVal('wpn', 'att', a.n1); },
		len:	function(a) { modctx.setVal('wpn', 'lgt', a.n1); },
		range:	function(a) { modctx.setVal('wpn', 'rng', a.n1); },
		ammo:	function(a) { modctx.setVal('wpn', 'shots', a.n1); },
		
		twohanded:	function(a) { modctx.setBoolean('wpn', '2h'); },
		armorpiercing:	function(a) { modctx.setBoolean('wpn', 'ap'); },
		armornegating:	function(a) { modctx.setBoolean('wpn', 'an'); },
		magic:	function(a) { modctx.setBoolean('wpn', 'magic'); },
		
		
		dt_stun:	function(a) { modctx.setWpnDamageType('dt_stun'); },
		dt_paralyze:	function(a) { modctx.setWpnDamageType('dt_paralyze'); },
		dt_poison:	function(a) { modctx.setWpnDamageType('dt_poison'); },
		dt_cap:	function(a) { modctx.setWpnDamageType('dt_cap'); },
		dt_demononly:	function(a) { modctx.setWpnDamageType('dt_demononly'); },
		dt_holy:	function(a) { modctx.setWpnDamageType('dt_holy'); },
		dt_magic:	function(a) { modctx.setWpnDamageType('dt_magic'); },
		dt_small:	function(a) { modctx.setWpnDamageType('dt_small'); },
		dt_large:	function(a) { modctx.setWpnDamageType('dt_large'); },
		dt_constructonly:	function(a) { modctx.setWpnDamageType('dt_constructonly'); },
		dt_raise:	function(a) { modctx.setWpnDamageType('dt_raise'); },
		
		mind:	function(a) { modctx.setBoolean('wpn', 'mind'); },
		cold:	function(a) { modctx.setBoolean('wpn', 'cold'); },
		fire:	function(a) { modctx.setBoolean('wpn', 'fire'); },
		shock:	function(a) { modctx.setBoolean('wpn', 'shock'); },
		poison:	function(a) { modctx.setBoolean('wpn', 'poison'); },
		
		
		//advanced weapon modding
		aoe:	function(a) { modctx.setVal('wpn', 'aoe', a.n1); },
		bonus:	function(a) { modctx.setBoolean('wpn', 'bonus'); },
		charge:	function(a) { modctx.setBoolean('wpn', 'charge'); },
		flail:	function(a) { modctx.setBoolean('wpn', 'flail'); },
		nostr:	function(a) { modctx.setBoolean('wpn', 'nostr'); },
		mrnegates:	function(a) { modctx.setBoolean('wpn', 'mres'); },
		mrnegateseasily:	function(a) { modctx.setBoolean('wpn', 'mreseasy'); },
		
		secondaryeffect:	function(a) { modctx.setVal('wpn', 'effect', a.n1); },
		secondaryeffectalways:	function(a) { modctx.setVal('wpn', 'effectauto', a.n1); }
		
				
	},
	
	//helpers
	dmg_types: {dt_stun:1, dt_paralyze:1, dt_poison:1, dt_cap:1, dt_demon:1, dt_demononly:1, dt_holy:1, dt_magic:1, dt_small:1, dt_large:1, dt_constructonly:1, dt_raise:1},
	setWpnDamageType: function(key) {
		if (modctx.wpn) {
			for (var k in modctx.dmg_types) {
				delete modctx.wpn[k];
			}
			modctx.wpn[key] = '1';
		} else throw 'command out of context';
	},
	
	setVal: function(tname, key, val) {
		if (modctx[tname]) 
			modctx[tname][key] = val;
		//else throw 'command out of context';
	},
	setEither: function(tname, key, val1, val2) {
		if (modctx[tname]) 
			modctx[tname][key] = val1 || val2;
		//else throw 'command out of context';
	},
	setBoolean: function(tname, key) {
		if (modctx[tname]) 
			modctx[tname][key] = '1';
		//else throw 'command out of context';
	},
	
	//member data
	loadedmods: [],
	
	//will be filled before we parse
	itemdata: undefined,
	itemlookup: undefined,
	item: null,
	
	armordata: undefined,
	armorlookup: undefined,
	armor: null,
	
	wpndata: undefined,
	wpnlookup: undefined,
	wpn: null
};

var modcom_re_multistr = /#(\w+)\s*"([^"]+)\r?$/;
var modcom_re_strcont  = /([^"]*)("|)/;
var modcom_re = /#(\w+)\s*("([^"]+)"|)(-?\d+\.?\d*|)\s*(-?\d+\.?\d*|)\s*(-?\d+\.?\d*|)\s*/;

function parseMod(str) {
	var lines = str.split('\n');
	for (var i=0; i<lines.length; i++) {
		var cstr = lines[i], i0 = i;
		var cmd, args;
		
		//check for open quote
		var a = modcom_re_multistr.exec(lines[i]);
		if (a) {
			cmd = a[1];
			args = { s: a[2] }
			
			//continue multi line string
			while(1) {
				i++;
				cstr += '\n' + lines[i];
				
				var a = modcom_re_strcont.exec(lines[i]);
				args.s += ' ' + a[1];
				//ended?
				if (a[2] == '"')
					break;
			}
			
		} else {
			//check single line commmand
			var a = modcom_re.exec(lines[i]);
			if (!a) continue;
			
			cmd = a[1];
			args = {
				s: a[3],
				n1: a[4],
				n2: a[5],
				n3: a[6]
			};
		}
		//process command
		if (cmd && modctx.commands[cmd]) {
			try {
				modctx.commands[cmd](args);
			}
			catch(err) {
				if (typeof(err) != 'string') throw(err);
					
				err = "ERROR: " + err;
				err += "\nmod line: "+i0;
				err += '\n'+cstr;
				console.log(err);
			}
			//debug.. attaches relevant mod commands to data (will show on details pane)
//			if (modctx.item) {
//				if (modctx.item.cmds===undefined) modctx.item.cmds = "";
//				modctx.item.cmds += cstr +'<br />';
//				modctx.item.cmds += JSON.stringify(args)+'<br />';
//			}
//			if (modctx.armor) {
//				if (modctx.armor.cmds===undefined) modctx.armor.cmds = "";
//				modctx.armor.cmds += cstr +'<br />';
//				modctx.armor.cmds += JSON.stringify(args)+'<br />';
//			}
		}
	}
}
